﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using Polly;
using System.Text.RegularExpressions;
using Microsoft.Extensions.DependencyInjection;
using Polly.Retry;

namespace Helpers
{
    public class HttpClientHelper
    {
        public const string s_userAgent = "User-Agent";
        public const string s_referer = "Referer";
        public const string s_cookie = "Cookie";
        public enum HttpClientNameEnum
        {
            smdo,
            proxy_noRedirect_noCookies,
            dynamicOverWall,
            noProxy_noRedirect_noCookies,
            noProxy_Redirect_Cookies,
            staticOverWall
        }

        public static HttpClient CreateHttpClient(Config config)
        {
            ServicePointManager.Expect100Continue = config.Expect100Continue;
            HttpClientHandler httpClientHandler = new HttpClientHandler()
            {
                AllowAutoRedirect = config.AllowAutoRedirect,
                AutomaticDecompression = config.AutomaticDecompression,
                UseCookies = config.UseCookies,
                Proxy = string.IsNullOrEmpty(config.ProxyEndpoint) ? null :
                new WebProxy(config.ProxyEndpoint)
                {
                    Credentials = new NetworkCredential(config.ProxyUserName, config.ProxyPassword)
                },
            };
            if (config.UseCookies)
                httpClientHandler.CookieContainer = config.CookieContainer;
            HttpClient httpClient = new HttpClient(httpClientHandler);
            httpClient.Timeout = config.Timeout;
            if (config.BaseAddress != null)
                httpClient.BaseAddress = config.BaseAddress;

            if (config.UserAgent != null)
                httpClient.DefaultRequestHeaders.Add("User-Agent", config.UserAgent);

            if (config.Accept != null)
                httpClient.DefaultRequestHeaders.Add("Accept", config.Accept);
            if (config.AcceptEncoding != null)
                httpClient.DefaultRequestHeaders.Add("Accept-Encoding", config.AcceptEncoding);
            if (config.AcceptLanguage != null)
                httpClient.DefaultRequestHeaders.Add("Accept-Language", config.AcceptLanguage);

            httpClient.DefaultRequestHeaders.ConnectionClose = config.ConnectionClose;

            for (int i = 0; i < config.DefaultRequestHeaders?.Count; i++)
            {
                var header = config.DefaultRequestHeaders.ElementAt(i);
                httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
            }
            return httpClient;
        }

        public static AsyncPolicy CreateHttpPolicy(int retryCount = int.MaxValue, HashSet<int> noRetryHttpCodes = null, Action<PolicyBuilder> addException = null)
        {
            var policy = Policy
                .Handle<System.Net.Http.HttpRequestException>(ex =>
                {
                    Console.WriteLine("fail Policy HttpRequestException--" + ex.Message);
                    var response = ex.Message;
                    bool f = int.TryParse(Regex.Match(response, @"\d{3}").Value, out int code);
                    return noRetryHttpCodes == null || !f || !noRetryHttpCodes.Contains(code);
                })
                .Or<System.Net.WebException>(ex =>
                {
                    var response = ex.Response as HttpWebResponse;
                    Console.WriteLine("fail Policy WebException--" + response.StatusCode);
                    //return (int)response.StatusCode != 404 && (int)response.StatusCode != 301 && (int)response.StatusCode != 302;
                    return noRetryHttpCodes == null || !noRetryHttpCodes.Contains((int)response.StatusCode);
                })
                .Or<System.Threading.Tasks.TaskCanceledException>(ex =>
                {
                    Console.WriteLine("fail Policy TaskCanceledException--" + ex.Message);
                    return true;
                })
                .Or<System.OperationCanceledException>(ex =>
                {
                    Console.WriteLine("fail Policy OperationCanceledException--" + ex.Message);
                    return true;
                });
            if (addException != null)
                addException(policy);
            return policy.RetryAsync(retryCount);
        }

        public static PolicyBuilder CreateHttpPolicyBuilder(HashSet<int> noRetryHttpCodes = null)
        {
            var policy = Policy
                .Handle<System.Net.Http.HttpRequestException>(ex =>
                {
                    Console.WriteLine("fail Policy HttpRequestException--" + ex.Message);
                    var response = ex.Message;
                    bool f = int.TryParse(Regex.Match(response, @"\d{3}").Value, out int code);
                    return noRetryHttpCodes == null || !f || !noRetryHttpCodes.Contains(code);
                })
                .Or<System.Net.WebException>(ex =>
                {
                    var response = ex.Response as HttpWebResponse;
                    Console.WriteLine("fail Policy WebException--" + response.StatusCode);
                    //return (int)response.StatusCode != 404 && (int)response.StatusCode != 301 && (int)response.StatusCode != 302;
                    return noRetryHttpCodes == null || !noRetryHttpCodes.Contains((int)response.StatusCode);
                })
                .Or<System.Threading.Tasks.TaskCanceledException>(ex =>
                {
                    Console.WriteLine("fail Policy TaskCanceledException--" + ex.Message);
                    return true;
                })
                .Or<System.OperationCanceledException>(ex =>
                {
                    Console.WriteLine("fail Policy OperationCanceledException--" + ex.Message);
                    return true;
                });
            return policy;
        }

        /// <summary>
        /// http配置
        /// 默认配置Accept，Accept-Encoding，Connection:close，AllowAutoRedirect:false，超时30秒
        /// </summary>
        public class Config
        {
            public string Accept { get; set; } = "text/html, application/xhtml+xml, image/jxr, */*";
            public string AcceptEncoding { get; set; } = "gzip, deflate";
            public string AcceptLanguage { get; set; } = "zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4";
            /// <summary>
            /// 默认不使用代理
            /// </summary>
            public string ProxyEndpoint { get; set; } = null;
            public string ProxyUserName { get; set; } = null;
            public string ProxyPassword { get; set; } = null;
            /// <summary>
            /// 默认不重定向
            /// </summary>
            public bool AllowAutoRedirect { get; set; } = false;
            public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            /// <summary>
            /// 默认不使用cookie,true-要设置CookieContainer，请求头的Cookie失效
            /// </summary>
            public bool UseCookies { get; set; } = false;
            public CookieContainer CookieContainer { get; set; } = new CookieContainer();
            /// <summary>
            /// 默认超时时间
            /// </summary>
            public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
            public Dictionary<string, string> DefaultRequestHeaders { get; set; } = new Dictionary<string, string>
            {
                //{ "Accept", "text/html, application/xhtml+xml, image/jxr, */*" },
                //{ "Accept-Encoding","gzip, deflate"},
            };
            public bool ConnectionClose { get; set; } = true;
            public bool Expect100Continue { get; set; } = false;
            public Uri BaseAddress { get; set; } = null;
            /// <summary>
            /// 用户代理
            /// </summary>
            public string UserAgent { get; set; } = null;
        }
    }

    /// <summary>
    /// 支持解压缩
    /// </summary>
    public class WebClientHelper : WebClient
    {
        protected override WebRequest GetWebRequest(Uri address)
        {
            HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest;
            request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
            return request;
        }
    }

}
